package compiler.semanal;

import java.util.LinkedList;
import java.util.List;

import compiler.abstree.AbsVisitor;
import compiler.abstree.tree.*;
import compiler.report.Report;

public class SemNameResolver implements AbsVisitor{

	private List<String> subProgNames=new LinkedList<String>();
	public boolean error=false;
	
	private void insertStandardFunctions() 
	{
		
		AbsVarDecl arg1 = new AbsVarDecl(new AbsDeclName("c"), new AbsAtomType(AbsAtomType.CHAR));
		AbsDecls params1 = new AbsDecls();
		params1.decls.add(arg1);
		AbsDecls decls1 = new AbsDecls();
		AbsBlockStmt stmt1 = new AbsBlockStmt(new AbsStmts());
		AbsProcDecl procDecl1 = new AbsProcDecl(new AbsDeclName("putch"), params1, decls1, stmt1);
		insertIntoSemTable(procDecl1.name.name,procDecl1);
		
		AbsVarDecl arg2 = new AbsVarDecl(new AbsDeclName("i"), new AbsAtomType(AbsAtomType.INT));
		AbsDecls params2 = new AbsDecls();
		params2.decls.add(arg2);
		AbsDecls decls2 = new AbsDecls();
		AbsBlockStmt stmt2 = new AbsBlockStmt(new AbsStmts());
		AbsProcDecl procDecl2 = new AbsProcDecl(new AbsDeclName("putint"), params2, decls2, stmt2);
		insertIntoSemTable(procDecl2.name.name,procDecl2);
		
		AbsDecls params3 = new AbsDecls();
		AbsDecls decls3 = new AbsDecls();
		AbsBlockStmt stmt3 = new AbsBlockStmt(new AbsStmts());
		AbsAtomType type3 = new AbsAtomType(AbsAtomType.CHAR);
		AbsFunDecl funDecl3 = new AbsFunDecl(new AbsDeclName("getch"), params3, type3, decls3, stmt3);
		insertIntoSemTable(funDecl3.name.name,funDecl3);
		
		AbsDecls params4 = new AbsDecls();
		AbsDecls decls4 = new AbsDecls();
		AbsBlockStmt stmt4 = new AbsBlockStmt(new AbsStmts());
		AbsAtomType type4 = new AbsAtomType(AbsAtomType.INT);
		AbsFunDecl funDecl4 = new AbsFunDecl(new AbsDeclName("getint"), params4, type4, decls4, stmt4);
		insertIntoSemTable(funDecl4.name.name,funDecl4);		
		
		AbsVarDecl arg5 = new AbsVarDecl(new AbsDeclName("c"), new AbsAtomType(AbsAtomType.CHAR));
		AbsDecls params5 = new AbsDecls();
		params5.decls.add(arg5);
		AbsDecls decls5 = new AbsDecls();
		AbsBlockStmt stmt5 = new AbsBlockStmt(new AbsStmts());
		AbsAtomType type5 = new AbsAtomType(AbsAtomType.INT);
		AbsFunDecl funDecl5 = new AbsFunDecl(new AbsDeclName("ord"), params5, type5, decls5, stmt5);
		insertIntoSemTable(funDecl5.name.name,funDecl5);
		
		AbsVarDecl arg6 = new AbsVarDecl(new AbsDeclName("i"), new AbsAtomType(AbsAtomType.INT));
		AbsDecls params6 = new AbsDecls();
		params6.decls.add(arg6);
		AbsDecls decls6 = new AbsDecls();
		AbsBlockStmt stmt6 = new AbsBlockStmt(new AbsStmts());
		AbsAtomType type6 = new AbsAtomType(AbsAtomType.CHAR);
		AbsFunDecl funDecl6 = new AbsFunDecl(new AbsDeclName("chr"), params6, type6, decls6, stmt6);
		insertIntoSemTable(funDecl6.name.name,funDecl6);	
		
		AbsVarDecl arg7 = new AbsVarDecl(new AbsDeclName("p"), new AbsPointerType(new AbsAtomType(AbsAtomType.INT)));
		AbsDecls params7 = new AbsDecls();
		params7.decls.add(arg7);
		AbsDecls decls7 = new AbsDecls();
		AbsBlockStmt stmt7 = new AbsBlockStmt(new AbsStmts());
		AbsProcDecl procDecl7 = new AbsProcDecl(new AbsDeclName("free"), params7, decls7, stmt7);
		insertIntoSemTable(procDecl7.name.name,procDecl7);				
	}
	
	@Override
	public void visit(AbsAlloc acceptor) 
	{
		acceptor.type.accept(this);		
	}

	@Override
	public void visit(AbsArrayType acceptor) 
	{
		acceptor.hiBound.accept(this);
		acceptor.loBound.accept(this);
		acceptor.type.accept(this);		
	}

	@Override
	public void visit(AbsAssignStmt acceptor) 
	{
		acceptor.srcExpr.accept(this);
		acceptor.dstExpr.accept(this);
	}

	@Override
	public void visit(AbsAtomConst acceptor) 
	{
		//DO NOTHING		
	}

	@Override
	public void visit(AbsAtomType acceptor) 
	{
		//DO NOTHING
	}

	@Override
	public void visit(AbsBinExpr acceptor) 
	{
		acceptor.fstExpr.accept(this);
		//recorda se ne mormo handlat tukaj
		if(acceptor.oper  != AbsBinExpr.RECACCESS)
		{
			acceptor.sndExpr.accept(this);
		}
	}

	@Override
	public void visit(AbsBlockStmt acceptor) 
	{
		acceptor.stmts.accept(this);	
	}

	@Override
	public void visit(AbsCallExpr acceptor) 
	{
		acceptor.name.accept(this);
		acceptor.args.accept(this);	
	}

	@Override
	public void visit(AbsConstDecl acceptor) 
	{
		acceptor.name.accept(this);
		acceptor.value.accept(this);	
	}

	@Override
	public void visit(AbsDeclName acceptor) 
	{
		AbsDecl decl = SemTable.fnd(acceptor.name);
		SemDesc.setNameDecl(acceptor, decl);	
	}
	
	@Override
	public void visit(AbsDecls acceptor) 
	{
		//deklaracije vstavimo v tabelo
		for(AbsDecl decl : acceptor.decls) 
		{
			String name="";
			if(decl instanceof AbsConstDecl) 
			{
				AbsConstDecl d = (AbsConstDecl)decl;		
				name = d.name.name;
				insertIntoSemTable(name, d);
			}
			else if(decl instanceof AbsTypeDecl) 
			{
				AbsTypeDecl d = (AbsTypeDecl)decl;
				name = d.name.name;
				insertIntoSemTable(name, d);
			}
			else if(decl instanceof AbsVarDecl) 
			{
				AbsVarDecl d = (AbsVarDecl)decl;
				name = d.name.name;
				insertIntoSemTable(name, d);
			}
			else if(decl instanceof AbsProcDecl) 
			{
				AbsProcDecl d = (AbsProcDecl)decl;
				name = d.name.name;
				insertIntoSemTable(name, d);
			}
			else if(decl instanceof AbsFunDecl) {
				AbsFunDecl d = (AbsFunDecl)decl;
				name = d.name.name;
				insertIntoSemTable(name, d);
			}
			
			if(subProgNames.contains(name)) 
			{
				Report.warning("Ime deklaracije je isto kot ime podprograma!", decl.begLine, decl.begColumn);
				error=true;
			}
		}			
		//gremo skozi uporabe
		for(AbsDecl decl : acceptor.decls) {
			decl.accept(this);
		}
	}

	@Override
	public void visit(AbsExprStmt acceptor) 
	{
		acceptor.expr.accept(this);	
	}

	@Override
	public void visit(AbsForStmt acceptor) 
	{
		acceptor.hiBound.accept(this);
		acceptor.loBound.accept(this);
		acceptor.name.accept(this);
		acceptor.stmt.accept(this);	
	}

	@Override
	public void visit(AbsFunDecl acceptor) 
	{
		acceptor.name.accept(this);
		
		SemTable.newScope();
		subProgNames.add(acceptor.name.name);
		
		//vstavimo parametre
		acceptor.pars.accept(this);
		//vstavimo deklaracije
		acceptor.decls.accept(this);
		//gremo v telo funkcije
		acceptor.stmt.accept(this);
		//preverimo vracajoc tip
		acceptor.type.accept(this);
		
		subProgNames.remove(acceptor.name.name);
		SemTable.oldScope();
	}

	@Override
	public void visit(AbsIfStmt acceptor) 
	{
		acceptor.cond.accept(this);
		acceptor.thenStmt.accept(this);
		acceptor.elseStmt.accept(this);
	}

	@Override
	public void visit(AbsNilConst acceptor) 
	{
		//DO NOTHING
	}

	@Override
	public void visit(AbsPointerType acceptor) 
	{
		acceptor.type.accept(this);
	}

	@Override
	public void visit(AbsProcDecl acceptor) 
	{
		acceptor.name.accept(this);

		SemTable.newScope();
		subProgNames.add(acceptor.name.name);
		
		//vstavimo parametre
		acceptor.pars.accept(this);
		//vstavimo deklaracije
		acceptor.decls.accept(this);
		//gremo v telo procedure
		acceptor.stmt.accept(this);
		
		subProgNames.remove(acceptor.name.name);
		SemTable.oldScope();
	}

	@Override
	public void visit(AbsProgram acceptor) 
	{
		this.insertStandardFunctions();
		subProgNames.add(acceptor.name.name);
		acceptor.decls.accept(this);
		acceptor.stmt.accept(this);
		acceptor.name.accept(this);
	}

	@Override
	public void visit(AbsRecordType acceptor) 
	{
		SemTable.newScope();
		acceptor.fields.accept(this);
		SemTable.oldScope();
	}

	@Override
	public void visit(AbsStmts acceptor) 
	{
		for(AbsStmt s : acceptor.stmts) {
			s.accept(this);
		}
	}

	@Override
	public void visit(AbsTypeDecl acceptor) 
	{
		acceptor.name.accept(this);
		acceptor.type.accept(this);
	}

	@Override
	public void visit(AbsTypeName acceptor) 
	{
		AbsDecl decl = SemTable.fnd(acceptor.name);		

		if(decl == null) {
			Report.warning("Simbol " + acceptor.name + " ni definiran!", acceptor.begLine, acceptor.begColumn);
			error=true;
			acceptor.error=true;
		}
		else
		{
			SemDesc.setNameDecl(acceptor, decl);
		}
	}

	@Override
	public void visit(AbsUnExpr acceptor) 
	{
		acceptor.expr.accept(this);
	}

	@Override
	public void visit(AbsValExprs acceptor) 
	{
		for(AbsValExpr e : acceptor.exprs) {
			e.accept(this);
		}
	}

	@Override
	public void visit(AbsValName acceptor) 
	{
		AbsDecl decl = SemTable.fnd(acceptor.name);		

		if(decl == null) {
			Report.warning("Simbol " + acceptor.name + " ni definiran!", acceptor.begLine, acceptor.begColumn);
			error=true;
			acceptor.error=true;
		}
		else
		{
			SemDesc.setNameDecl(acceptor, decl);
		}
	}

	@Override
	public void visit(AbsVarDecl acceptor) 
	{
		acceptor.name.accept(this);
		acceptor.type.accept(this);
	}

	@Override
	public void visit(AbsWhileStmt acceptor) 
	{
		acceptor.cond.accept(this);
		acceptor.stmt.accept(this);
	}
	
	/**
	 * Poskusa vstaviti deklaracijo v simbolno tabelo. Ce istem nivoju ze obstaja deklaracija z istim imenov vrze error.
	 * @param name
	 * @param decl
	 */
	private void insertIntoSemTable(String name, AbsDecl decl) 
	{		
		try 
		{
			SemTable.ins(name, decl);
		}
		catch(SemIllegalInsertException e) 
		{
			Report.warning("Simbol " + name + " je za ta nivo ze definiran!", decl.begLine, decl.begColumn);
			decl.error=true;
			error = true;
		}	
	}

}
